package net.xdclass.coupon.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.common.JsonData;
import net.xdclass.common.PageJsonData;
import net.xdclass.coupon.enums.CouponCategory;
import net.xdclass.coupon.enums.CouponStatus;
import net.xdclass.coupon.enums.CouponUseStatus;
import net.xdclass.coupon.mapper.CouponMapper;
import net.xdclass.coupon.mapper.CouponRecordMapper;
import net.xdclass.coupon.model.CouponDO;
import net.xdclass.coupon.model.CouponRecordDO;
import net.xdclass.coupon.request.GrantCouponRequest;
import net.xdclass.coupon.service.CouponService;
import net.xdclass.coupon.vo.CouponVO;
import net.xdclass.common.enums.BizCodeEnum;
import net.xdclass.common.exception.BizException;
import net.xdclass.local.ThreadLocal;
import net.xdclass.common.model.LoginUser;
import net.xdclass.common.utils.CommonUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yanghuanxi
 * @since 2023-12-05
 */
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {

    @Autowired
    private CouponMapper couponMapper;
    @Autowired
    private CouponRecordMapper recordMapper;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分页查询优惠券
     *
     * @param currentPage
     * @param size
     * @return
     */
    @Override
    public JsonData pageCoupon(int currentPage, int size) {
        IPage<CouponDO> page = new Page<>(currentPage, size);
        QueryWrapper<CouponDO> wrapper = new QueryWrapper<>();
        wrapper.eq("publish", CouponStatus.PUBLISH)
                .eq("category", CouponCategory.PROMOTION)
                .orderByDesc("create_time");
        IPage<CouponDO> pageData = couponMapper.selectPage(page, wrapper);
        JsonData pageJsonData = new PageJsonData(currentPage, size, (int) pageData.getTotal());
        List<CouponVO> records = pageData.getRecords().stream().map(this::toVO).collect(Collectors.toList());
        pageJsonData.setData(records);
        return pageJsonData;
    }

    /**
     * 领取优惠券
     * 1、优惠券是否存在
     * 2、校验优惠券是否可以领取：时间、库存、超过限制
     * 3、扣减库存
     * 4、保存领券记录
     *
     * @param id
     * @param category
     * @return
     */
    @Transactional
    @Override
    public JsonData drawCoupon(Long id, CouponCategory category) {
        LoginUser loginUser = ThreadLocal.getLoginUser();
        QueryWrapper<CouponDO> wrapper = new QueryWrapper<>();
        wrapper.eq("id", id)
                .eq("category", category)
                .eq("publish", CouponStatus.PUBLISH);
        String lockName = "lock:coupon:" + id;
        RLock lock = redissonClient.getLock(lockName);
        lock.lock();
        try {
            CouponDO couponDO = couponMapper.selectOne(wrapper);
            validCoupon(couponDO, loginUser.getId());
            // 添加领取记录
            CouponRecordDO couponRecordDO = new CouponRecordDO();
            BeanUtils.copyProperties(couponDO, couponRecordDO);
            couponRecordDO.setCreateTime(new Date());
            couponRecordDO.setUseState(CouponUseStatus.NEW);
            couponRecordDO.setUserId(loginUser.getId());
            couponRecordDO.setUserName(loginUser.getName());
            couponRecordDO.setCouponId(couponDO.getId());
            couponRecordDO.setId(null);
            // 扣减库存
            int line = couponMapper.reduceStock(couponDO.getId());
            if (line == 1) {
                recordMapper.insert(couponRecordDO);
            } else {
                log.warn("用户【{}】领取优惠券【{}】:【{}】失败.", loginUser.getId(), couponDO.getId(), couponDO.getCouponTitle());
                throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
            }
            return JsonData.buildSuccess();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 发放优惠券【for new user】
     *
     * @param request
     * @return
     */
    @Transactional
    @Override
    public JsonData grantCoupon(GrantCouponRequest request) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(request.getUserId());
        loginUser.setName(request.getName());
        // 【领取优惠券】方法中会用到
        ThreadLocal.setLoginUser(loginUser);
        QueryWrapper<CouponDO> wrapper = new QueryWrapper<>();
        wrapper.eq("category", CouponCategory.NEW_USER);
        List<CouponDO> couponDOS = couponMapper.selectList(wrapper);
        if (CollectionUtils.isEmpty(couponDOS)) return JsonData.buildSuccess();
        for (CouponDO couponDO : couponDOS) {
            this.drawCoupon(couponDO.getId(), CouponCategory.NEW_USER);
        }
        return JsonData.buildSuccess();
    }

    /**
     * 校验优惠券是否可领取
     * @param couponDO
     * @param userId
     */
    private void validCoupon(CouponDO couponDO, Long userId) {
        if (couponDO == null || userId == null){
            throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
        }
        // 校验库存
        if (couponDO.getStock() <= 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }
        // 校验时间
        long time = CommonUtil.getCurrnetTimeStamp();
        long start = couponDO.getStartTime().getTime();
        long end = couponDO.getEndTime().getTime();
        if (time < start || time > end){
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }
        // 校验是否超过领取限制
        QueryWrapper<CouponRecordDO> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId).eq("coupon_id", couponDO.getId());
        Integer count = recordMapper.selectCount(wrapper);
        if (couponDO.getUserLimit() <= count){
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }

    private CouponVO toVO(CouponDO d) {
        CouponVO vo = new CouponVO();
        BeanUtils.copyProperties(d, vo);
        return vo;
    }
}
